#include "StdAfx.h"
#include "Hebbian.h"

//constructor
CHebbian::CHebbian(int n_input, int n_output)
{
	n_netinput = n_input;
	n_netoutput = n_output;
	LearningRate = 0.1;
	c_hebbian = (double**)malloc(sizeof(double*)*n_netinput);
	for(int i=0; i<n_input; i++)
	{
		c_hebbian[i] = (double*)calloc(n_output, sizeof(double));
	}
}

//Load the hebbian from file
CHebbian::CHebbian(string filepath)
{
	vector<vector<double>> hebbiandata;
	unsigned int size1, size2;
	try
	{
		//Load the data
		bf.LoadBinaryData(hebbiandata, filepath);
		double** doubledata = bf.VectorToPointer(hebbiandata, size1, size2);
		//Create the hebbian links
		n_netinput = (int)hebbiandata.size();
		n_netoutput = (int)hebbiandata[0].size();
		c_hebbian = (double**)malloc(sizeof(double*)*n_netinput);
		for(int i=0; i<n_netinput; i++)
		{
			c_hebbian[i] = (double*)calloc(n_netoutput, sizeof(double));
		}
		//Load the weights into the links from the matrix
		CHebbian::NetLoadFromMatrix(doubledata, size1, size2);
	}
	catch(string exceptionmessage)
	{
		::AfxMessageBox(CString(exceptionmessage.c_str()), MB_OK, 0);
		throw exceptionmessage;
	}
}

//train the network
void CHebbian::NetTraining(double* c_input, double* c_output, int* s_input, int* s_output, int trainNum)
{
	//validate if the data are loaded from a matrix
	if(status_loaded == 1)
	{
		::AfxMessageBox(CString("Data are loaded from matrix, can't be manually trained"), MB_OK, 0);
		return;
	}
	for(int i=0; i<trainNum; i++)
	{
		if(s_input[i]>=n_netinput)
		{
			::AfxMessageBox(CString("incorrect input sequence"), MB_OK, 0);
			return;
		}
		if(s_output[i]>=n_netoutput)
		{
			::AfxMessageBox(CString("incorrect output sequence"), MB_OK, 0);
			return;
		}
		UpdateWeight(c_input[i], c_output[i], s_input[i], s_output[i]);
	}
	status_trained = 1;
	return;
}

//update weight
void CHebbian::UpdateWeight(double c_input, double c_output, int cur_input, int cur_output)
{
	//if(m_Max == m_Min)		//if map has not been rescaled
	c_hebbian[cur_input][cur_output] += LearningRate*c_input*c_output;
	//else									//if map has been rescaled
	//	c_hebbian[cur_input][cur_output] += (LearningRate*c_input*c_output-m_Min)/(m_Max-m_Min);
	//return;
}

//find the associated output
int CHebbian::NetFindOutput(int cur_input)
{
	if(status_associated!=1)
	{
		::AfxMessageBox(CString("network not associated!"), MB_OK, 0);
		return -1;
	}
	return ass_netinput[cur_input];
}

//find the associated input
int CHebbian::NetFindInput(int cur_output)
{
	if(status_associated!=1)
	{
		::AfxMessageBox(CString("network not associated!"), MB_OK, 0);
		return -1;
	}
	return ass_netoutput[cur_output];
}

CHebbian::~CHebbian(void)
{
}

//set the learning rate
void CHebbian::NetSetLearningRate(double lr)
{
	LearningRate = lr;
	return;
}

//associated output with input and input with output
void CHebbian::NetAssociated(void)
{
	if(status_trained==0)
	{
		::AfxMessageBox(CString("linked not established"), MB_OK, 0);
		return;
	}
	ass_netinput = (int*)malloc(sizeof(int)*n_netinput);
	ass_netoutput = (int*)malloc(sizeof(int*)*n_netoutput);
//associat output to input
	for(int i=0; i<n_netinput; i++)
	{
		double maxtemp = 0;
		int maxindex = -1;
		for(int j=0; j<n_netoutput; j++)
		{
			if(c_hebbian[i][j]>maxtemp)
			{
				maxtemp = c_hebbian[i][j];
				maxindex = j;
			}
		}
		ass_netinput[i] = maxindex;
	}
//associat input with output
	for(int j=0; j<n_netoutput; j++)
	{
		double maxtemp = 0;
		int maxindex = -1;
		for(int i=0; i<n_netinput; i++)
		{
			if(c_hebbian[i][j]>maxtemp)
			{
				maxtemp = c_hebbian[i][j];
				maxindex = i;
			}
		}
		ass_netoutput[j] = maxindex;
	}
	status_associated = 1;
	return;
}

//destroy the network
bool CHebbian::NetDestroy(void)
{
	for(int i=0; i<n_netinput; i++)
	{
		free(c_hebbian[i]);
	}
	free(ass_netinput);
	free(ass_netoutput);
	return 1;
}

//rescale the hebbian links to keep away from infinity
void CHebbian::NetRescale(void)
{
	//define the min and max of the hebbian link
	double m_Max = 9999999999999;
	double m_Min = 0;
	//search for the min and max
	for(int j=0; j<n_netinput; j++)
	{
		for(int i=0; i<n_netoutput; i++)
		{
			if(c_hebbian[j][i]<m_Min)
				m_Min = c_hebbian[j][i];
			if(c_hebbian[j][i]>m_Max)
				m_Max = c_hebbian[j][i];
		}
	}
	//if min equals to max, don't rescale and return
	if(m_Max == m_Min)
		return;
	//rescale the weights
	for(int j=0; j<n_netinput; j++)
		for(int i=0; i<n_netoutput; i++)
			c_hebbian[j][i] = (c_hebbian[j][i]-m_Min)/(m_Max-m_Min);
	return;
}

//get the links
double** CHebbian::NetGetHebbian(void)
{
	return c_hebbian;
}

//load the hebbian from matrix
void CHebbian::NetLoadFromMatrix(double** m_Double, int m_Size1, int m_Size2)
{
	//load the size
	n_netinput = m_Size1;
	n_netoutput = m_Size2;
	//load the links
	for(int i=0; i<n_netinput; i++)
	{
		for(int j=0; j<n_netoutput; j++)
		{
			c_hebbian[i][j] = m_Double[i][j];
		}
	}
	//change the flag
	status_loaded = 1;
	//set as trained
	status_trained = 1;
	//associate input with ouput and output with inputs
	CHebbian::NetAssociated();
	return;
}

//Save the hebbian links to file
void CHebbian::NetSaveToFile(string filepath)
{
	//Get the hebbian matrix
	double** hebbianmatrix = CHebbian::NetGetHebbian();
	vector<vector<double>> hebbianvector = bf.PointerToVector(hebbianmatrix, (unsigned int)n_netinput, (unsigned int)n_netoutput);
	bf.SaveDataAsBinary(hebbianvector, filepath);
	return;
}
